﻿using System;
using System.Collections.Specialized;
using System.Text.RegularExpressions;

namespace Oxage.Common
{
	/// <summary>
	/// Command line arguments parser.<br />
	/// CodeProject article on http://www.codeproject.com/KB/recipes/command_line.aspx by GriffonRL
	/// </summary>
	public partial class Arguments
	{
		#region Private members
		private StringDictionary parameters;
		private string[] originalArray; //Original parsed command line to string array
		private string originalString; //Original command line string
		#endregion

		#region Constructor
		/// <summary>
		/// Class constructor.
		/// </summary>
		/// <param name="args">List of arguments.</param>
		public Arguments(string[] args)
		{
			//Save original array
			this.originalArray = args;

			//Reconstruct original command line string
			this.originalString = string.Empty;
			foreach (string arg in args)
			{
				this.originalString += " " + arg;
			}

			//Parse parameters
			this.parameters = new StringDictionary();
			string parameter = null;
			string[] parts = null;
			Regex spliter = new Regex(@"^-{1,2}|^/|=|:", RegexOptions.IgnoreCase | RegexOptions.Compiled);
			Regex remover = new Regex(@"^['""]?(.*?)['""]?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

			// Valid parameters forms:
			// {-,/,--}param{ ,=,:}((",')value(",'))
			// Examples: 
			// -param1 value1 --param2 /param3:"Test-:-work" 
			//   /param4=happy -param5 '--=nice=--'
			foreach (string arg in args)
			{
				// Look for new parameters (-,/ or --) and a
				// possible enclosed value (=,:)
				parts = spliter.Split(arg, 3);
				switch (parts.Length)
				{
					// Found a value (for the last parameter 
					// found (space separator))
					case 1:
						if (parameter != null)
						{
							if (!this.parameters.ContainsKey(parameter))
							{
								parts[0] = remover.Replace(parts[0], "$1");
								this.parameters.Add(parameter, parts[0]);
							}
							parameter = null;
						}
						// else Error: no parameter waiting for a value (skipped)
						break;

					// Found just a parameter

					case 2:
						// The last parameter is still waiting. 
						// With no value, set it to true.
						if (parameter != null)
						{
							if (!this.parameters.ContainsKey(parameter))
							{
								this.parameters.Add(parameter, "");
							}
						}
						parameter = parts[1];
						break;

					// Parameter with enclosed value
					case 3:
						// The last parameter is still waiting. 
						// With no value, set it to true.
						if (parameter != null)
						{
							if (!this.parameters.ContainsKey(parameter))
							{
								this.parameters.Add(parameter, "");
							}
						}
						parameter = parts[1];

						// Remove possible enclosing characters (",')
						if (!this.parameters.ContainsKey(parameter))
						{
							parts[2] = remover.Replace(parts[2], "$1");
							this.parameters.Add(parameter, parts[2]);
						}
						parameter = null;
						break;
				}
			}

			// In case a parameter is still waiting
			if (parameter != null && !this.parameters.ContainsKey(parameter))
			{
				this.parameters.Add(parameter, "");
			}
		}
		#endregion

		#region Public properties
		/// <summary>
		/// Gets the parameter value by key.
		/// </summary>
		/// <param name="key">Argument key (e.g. for argument "-path=C:\temp" the key would be "path")</param>
		/// <returns>Returns a parameter value.</returns>
		public string this[string key]
		{
			get
			{
				return this.parameters[key];
			}
		}

		/// <summary>
		/// Gets the number of parsed arguments.
		/// </summary>
		public int Count
		{
			get
			{
				return parameters.Count;
			}
		}
		#endregion

		#region Public methods
		/// <summary>
		/// Check if arguments contain key.
		/// </summary>
		/// <param name="key">Argument key (e.g. for argument "-path=C:\temp" the key would be "path")</param>
		/// <returns>Return true if arguments contain key.</returns>
		public bool Contains(string key)
		{
			return this.parameters.ContainsKey(key);
		}

		/// <summary>
		/// Get the original string that has been passed to the command line.
		/// </summary>
		/// <returns>Returns command line arguments in one string.</returns>
		public override string ToString()
		{
			return this.originalString;
		}

		/// <summary>
		/// Get the list of arguments that have been passed to the command line.
		/// </summary>
		/// <returns>Returns command line arguments in array.</returns>
		public string[] ToArray()
		{
			return this.originalArray;
		}
		#endregion

		#region Static methods
		/// <summary>
		/// Split a single string to list of arguments.
		/// </summary>
		/// <param name="arguments">Command line arguments in a string.</param>
		/// <returns>Returns list of arguments.</returns>
		public static string[] Parse(string arguments)
		{
			string[] result = arguments.Split(' '); //TODO: Split by Regex
			return result;
		}

		/// <summary>
		/// Create an Arguments object.
		/// </summary>
		/// <param name="arguments">Command line arguments in a string.</param>
		/// <returns>Returns a new Arguments object.</returns>
		public static Arguments Create(string arguments)
		{
			string[] args = Arguments.Parse(arguments);
			Arguments result = new Arguments(args);
			return result;
		}
		#endregion
	}
}
